#include"shaderClass.h"
#include<glad/glad.h>

namespace OCPP
{
	// Reads a text file and outputs a string with everything in the text file
	std::string get_file_contents(String filename)
	{
		std::ifstream in(filename, std::ios::binary);
		if (in)
		{
			std::string contents;
			in.seekg(0, std::ios::end);
			contents.resize(in.tellg());
			in.seekg(0, std::ios::beg);
			in.read(&contents[0], contents.size());
			in.close();
			return(contents);
		}
		throw(errno);
	}

	Shader::Shader()
	{

	}
	
	void Shader::load(String vertexFile, String fragmentFile, String geomFile)
	{
		// Read vertexFile and fragmentFile and store the strings
		std::string vertexCode = get_file_contents(vertexFile);
		std::string fragmentCode = get_file_contents(fragmentFile);

		// Convert the shader source strings into character arrays
		const char* vertexSource = vertexCode.c_str();
		const char* fragmentSource = fragmentCode.c_str();

		// Create Vertex Shader Object and get its reference
		GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
		// Attach Vertex Shader source to the Vertex Shader Object
		glShaderSource(vertexShader, 1, &vertexSource, NULL);
		// Compile the Vertex Shader into machine code
		glCompileShader(vertexShader);
		// Checks if Shader compiled succesfully
		compileErrors(vertexShader, "VERTEX");

		// Create Fragment Shader Object and get its reference
		GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
		// Attach Fragment Shader source to the Fragment Shader Object
		glShaderSource(fragmentShader, 1, &fragmentSource, NULL);
		// Compile the Vertex Shader into machine code
		glCompileShader(fragmentShader);
		// Checks if Shader compiled succesfully
		compileErrors(fragmentShader, "FRAGMENT");

		// Create Shader Program Object and get its reference
		ID = glCreateProgram();
		// Attach the Vertex and Fragment Shaders to the Shader Program
		glAttachShader(ID, vertexShader);
		glAttachShader(ID, fragmentShader);

		GLuint geometryShader;
		bool hasGeomShader = geomFile != "";
		if (hasGeomShader)
		{
			String geometryCode = get_file_contents(geomFile);
			const char* geometrySource = geometryCode.c_str();
			// Create Geometry Shader Object and get its reference
			geometryShader = glCreateShader(GL_GEOMETRY_SHADER);
			// Attach Geometry Shader source to the Fragment Shader Object
			glShaderSource(geometryShader, 1, &geometrySource, NULL);
			// Compile the Geometry Shader into machine code
			glCompileShader(geometryShader);
			// Checks if Shader compiled succesfully
			compileErrors(geometryShader, "GEOMETRY");

			glAttachShader(ID, geometryShader);
		}

		// Wrap-up/Link all the shaders together into the Shader Program
		glLinkProgram(ID);
		compileErrors(ID, "PROGRAM");

		// Delete the now useless Vertex and Fragment Shader objects
		glDeleteShader(vertexShader);
		glDeleteShader(fragmentShader);

		if (hasGeomShader)
		{
			glDeleteShader(geometryShader);
		}

		mLoaded = true;
	}

	Shader::~Shader()
	{
		glDeleteProgram(ID);
	}

	// Activates the Shader Program
	void Shader::Activate()
	{
		glUseProgram(ID);
	}

	// Checks if the different Shaders have compiled properly
	void Shader::compileErrors(unsigned int shader, const char* type)
	{
		// Stores status of compilation
		GLint hasCompiled;
		// Character array to store error message in
		char infoLog[1024];
		if (type != "PROGRAM")
		{
			glGetShaderiv(shader, GL_COMPILE_STATUS, &hasCompiled);
			if (hasCompiled == GL_FALSE)
			{
				glGetShaderInfoLog(shader, 1024, NULL, infoLog);
				std::cout << "SHADER_COMPILATION_ERROR for:" << type << "\n" << infoLog << std::endl;
			}
		}
		else
		{
			glGetProgramiv(shader, GL_LINK_STATUS, &hasCompiled);
			if (hasCompiled == GL_FALSE)
			{
				glGetProgramInfoLog(shader, 1024, NULL, infoLog);
				std::cout << "SHADER_LINKING_ERROR for:" << type << "\n" << infoLog << std::endl;
			}
		}
	}

	void Shader::setUniform1f(String name, float value)
	{
		GLint uniID = glGetUniformLocation(ID, name.c_str());
		glUniform1f(uniID, value);
	}

	void Shader::setUniform1i(String name, uint32 value)
	{
		GLint uniID = glGetUniformLocation(ID, name.c_str());
		glUniform1i(uniID, value);
	}

	void Shader::setUniform4fv(String name, const Vector4& value)
	{
		GLint uniID = glGetUniformLocation(ID, name.c_str());
		glUniform4fv(uniID, 1, &(value.x));
	}

	void Shader::setUniform3fv(String name, const Vector3& value)
	{
		GLint uniID = glGetUniformLocation(ID, name.c_str());
		glUniform3fv(uniID, 1, &(value.x));
	}

	void Shader::setUniformMatrix4fv(String name, const Matrix4& value)
	{
		GLint uniID = glGetUniformLocation(ID, name.c_str());
		glUniformMatrix4fv(uniID, 1, GL_TRUE, &(value[0][0]));
	}

	void Shader::setUniformMatrix4fv(String name, const glm::mat4& value)
	{
		GLint uniID = glGetUniformLocation(ID, name.c_str());
		glUniformMatrix4fv(uniID, 1, GL_FALSE, glm::value_ptr(value));
	}
}
